﻿using EMall.Application;
using EMall.Query;
using EMall.Uow;
using IdentityModel;
using IdentityServer4.Models;
using Microsoft.EntityFrameworkCore;
using System.Linq;

namespace EMall.IdentityService.IdentityServers
{
    public class IdentityServerAppService : ApplicationService, IIdentityServerAppService
    {
        private readonly IIdentityServerRepository _identityServerRepository;
        public IdentityServerAppService(IIdentityServerRepository identityServerRepository)
        {
            _identityServerRepository = identityServerRepository;
        }
        #region Client
        [UnitOfWork]
        public async Task CreateClientAsync(CreateClientDto input)
        {
            var client = new Client
            {
                ClientId = input.ClientId,
                ClientName = input.ClientName,
                ClientUri = input.ClientUri,
                ClientSecrets = input.ClientSecrets.TrimEnd(',').Split(',')
                .Select(secret => new IdentityServer4.Models.Secret(secret.ToSha256())).ToList(),
                RedirectUris = input.RedirectUris.TrimEnd(',').Split(','),
                PostLogoutRedirectUris = input.PostLogoutRedirectUris.TrimEnd(',').Split(','),
                AllowedGrantTypes = input.AllowedGrantTypes.TrimEnd(',').Split(','),
                AllowedScopes = input.AllowedScopes.TrimEnd(',').Split(','),
                Enabled = input.Enabled,
            };
            await _identityServerRepository.AddClientAsync(client.ToEntity());
        }
        [UnitOfWork]
        public async Task UpdateClientAsync(UpdateClientDto input)
        {
            var client = await QueryService.GetQueryable<IdentityServer4.EntityFramework.Entities.Client>()
                .Include(x => x.AllowedCorsOrigins)
                .Include(x => x.AllowedGrantTypes)
                .Include(x => x.AllowedScopes)
                .Include(x => x.Claims)
                .Include(x => x.ClientSecrets)
                .Include(x => x.IdentityProviderRestrictions)
                .Include(x => x.PostLogoutRedirectUris)
                .Include(x => x.Properties)
                .Include(x => x.RedirectUris)
                .FirstOrDefaultAsync(o => o.Id == input.Id);
            var updateModel = new Client
            {
                ClientUri = input.ClientUri,
                RedirectUris = input.RedirectUris.TrimEnd(',').Split(','),
                PostLogoutRedirectUris = input.PostLogoutRedirectUris.TrimEnd(',').Split(','),
                AllowedGrantTypes = input.AllowedGrantTypes.TrimEnd(',').Split(','),
                AllowedScopes = input.AllowedScopes.TrimEnd(',').Split(','),
                Enabled = input.Enabled,
            }.ToEntity();
            client.ClientUri = updateModel.ClientUri;
            client.RedirectUris = updateModel.RedirectUris;
            client.PostLogoutRedirectUris = updateModel.PostLogoutRedirectUris;
            client.AllowedGrantTypes = updateModel.AllowedGrantTypes;
            client.AllowedScopes = updateModel.AllowedScopes;
            client.Enabled = updateModel.Enabled;
        }
        [UnitOfWork]
        public async Task DeleteClientAsync(DeleteClientDto input)
        {
            var client = await QueryService.GetQueryable<IdentityServer4.EntityFramework.Entities.Client>().FirstOrDefaultAsync(o => o.Id == input.Id);
            await _identityServerRepository.RemoveClientAsync(client);
        }
        public async Task<IdentityServer4.EntityFramework.Entities.Client> GetClientAsync(int id)
        {
            var data = await QueryService.GetQueryable<IdentityServer4.EntityFramework.Entities.Client>()
                .Include(x => x.AllowedCorsOrigins)
                .Include(x => x.AllowedGrantTypes)
                .Include(x => x.AllowedScopes)
                .Include(x => x.Claims)
                .Include(x => x.ClientSecrets)
                .Include(x => x.IdentityProviderRestrictions)
                .Include(x => x.PostLogoutRedirectUris)
                .Include(x => x.Properties)
                .Include(x => x.RedirectUris)
                .FirstOrDefaultAsync(o => o.Id == id);
            return data;
        }
        public async Task<IPagedList<IdentityServer4.EntityFramework.Entities.Client>> GetClientPagedListAsync(GetClientListPagingAndSortingQueryDto input)
        {
            var data = await QueryService.GetQueryable<IdentityServer4.EntityFramework.Entities.Client>()
                .Include(x => x.AllowedCorsOrigins)
                .Include(x => x.AllowedGrantTypes)
                .Include(x => x.AllowedScopes)
                .Include(x => x.Claims)
                .Include(x => x.ClientSecrets)
                .Include(x => x.IdentityProviderRestrictions)
                .Include(x => x.PostLogoutRedirectUris)
                .Include(x => x.Properties)
                .Include(x => x.RedirectUris)
                .Sorting(input.Sorting)
                .ToPagedListAsync(input.PageIndex, input.PageSize);
            return data;
        }
        #endregion
        #region ApiResource
        [UnitOfWork]
        public async Task CreateApiResourceAsync(CreateApiResourceDto input)
        {
            var apiResource = new IdentityServer4.Models.ApiResource
            {
                Name = input.Name,
                Properties =
                {
                    { "Destinations",input.Destinations.TrimEnd(',') }
                },
                Scopes = input.Scopes.TrimEnd(',').Split(',')
                .Select(o => $"{input.Name}.{o}").ToList(),
            };
            await _identityServerRepository.AddApiResourceAsync(apiResource.ToEntity());
            var apiScopes = apiResource.Scopes.Select(o => new ApiScope(o));
            foreach (var apiScope in apiScopes)
            {
                await _identityServerRepository.AddApiScopeAsync(apiScope.ToEntity());
            }
        }
        [UnitOfWork]
        public async Task UpdateApiResourceAsync(UpdateApiResourceDto input)
        {
            var apiResource = await QueryService.GetQueryable<IdentityServer4.EntityFramework.Entities.ApiResource>()
                .Include(x => x.Secrets)
                .Include(x => x.Scopes)
                .Include(x => x.UserClaims)
                .Include(x => x.Properties)
                .FirstOrDefaultAsync(o => o.Id == input.Id);
            var updateModel = new IdentityServer4.Models.ApiResource
            {
                Properties =
                {
                    { "Destinations",input.Destinations.TrimEnd(',') }
                },
                Scopes = input.Scopes.TrimEnd(',').Split(',')
                .Select(o => $"{apiResource.Name}.{o}").ToList(),
            }.ToEntity();
            var deleteScopes = apiResource.Scopes.Select(o => o.Scope).Except(updateModel.Scopes.Select(s => s.Scope));
            // 同步删除ApiScopes
            var deleteApiScopes = await QueryService.GetQueryable<IdentityServer4.EntityFramework.Entities.ApiScope>()
                .Where(o => deleteScopes.Contains(o.Name))
                .ToListAsync();
            foreach (var apiScope in deleteApiScopes)
            {
                await _identityServerRepository.RemoveApiScopeAsync(apiScope);
            }
            // 同步删除ClientScopes
            var deleteClientScopes = await QueryService.GetQueryable<IdentityServer4.EntityFramework.Entities.ClientScope>()
                .Where(o => deleteScopes.Contains(o.Scope))
                .ToListAsync();
            foreach (var clientScope in deleteClientScopes)
            {
                await _identityServerRepository.RemoveClientScopeAsync(clientScope);
            }
            // 同步添加ApiScopes
            var addApiScopes = updateModel.Scopes.Select(o => o.Scope).Except(apiResource.Scopes.Select(s => s.Scope)).Select(o => new ApiScope(o));
            foreach (var apiScope in addApiScopes)
            {
                await _identityServerRepository.AddApiScopeAsync(apiScope.ToEntity());
            }

            apiResource.Properties = updateModel.Properties;
            apiResource.Scopes = updateModel.Scopes;
        }
        [UnitOfWork]
        public async Task DeleteApiResourceAsync(DeleteApiResourceDto input)
        {
            var apiResource = await QueryService.GetQueryable<IdentityServer4.EntityFramework.Entities.ApiResource>()
                .FirstOrDefaultAsync(o => o.Id == input.Id);
            await _identityServerRepository.RemoveApiResourceAsync(apiResource);
            var apiScopes = await QueryService.GetQueryable<IdentityServer4.EntityFramework.Entities.ApiScope>()
                .Where(o => apiResource.Scopes.Select(s => s.Scope).Contains(o.Name))
                .ToListAsync();
            foreach (var apiScope in apiScopes)
            {
                await _identityServerRepository.RemoveApiScopeAsync(apiScope);
            }
        }
        public async Task<IdentityServer4.EntityFramework.Entities.ApiResource> GetApiResourceAsync(int id)
        {
            var data = await QueryService.GetQueryable<IdentityServer4.EntityFramework.Entities.ApiResource>()
                .Include(x => x.Secrets)
                .Include(x => x.Scopes)
                .Include(x => x.UserClaims)
                .Include(x => x.Properties)
                .FirstOrDefaultAsync(o => o.Id == id);
            return data;
        }
        public async Task<IPagedList<IdentityServer4.EntityFramework.Entities.ApiResource>> GetApiResourcePagedListAsync(GetApiResourceListPagingAndSortingQueryDto input)
        {
            var data = await QueryService.GetQueryable<IdentityServer4.EntityFramework.Entities.ApiResource>()
                .Include(x => x.Secrets)
                .Include(x => x.Scopes)
                .Include(x => x.UserClaims)
                .Include(x => x.Properties)
                .Sorting(input.Sorting)
                .ToPagedListAsync(input.PageIndex, input.PageSize);
            return data;
        }
        #endregion
        #region ApiScope
        [UnitOfWork]
        public async Task CreateApiScopeAsync(CreateApiScopeDto input)
        {
            var claims = input.Claims.SplitToList();
            var apiScope = new IdentityServer4.Models.ApiScope(input.Name, claims);
            await _identityServerRepository.AddApiScopeAsync(apiScope.ToEntity());
        }
        [UnitOfWork]
        public async Task UpdateApiScopeAsync(UpdateApiScopeDto input)
        {
            var apiScope = await QueryService.GetQueryable<IdentityServer4.EntityFramework.Entities.ApiScope>()
                .Include(x => x.UserClaims)
                .Include(x => x.Properties)
                .FirstOrDefaultAsync(o => o.Id == input.Id);
            var claims = input.Claims.SplitToList();
            var updateModel = new IdentityServer4.Models.ApiScope(input.Name, claims).ToEntity();
            apiScope.UserClaims = updateModel.UserClaims;
        }
        [UnitOfWork]
        public async Task DeleteApiScopeAsync(DeleteApiScopeDto input)
        {
            var apiScope = await QueryService.GetQueryable<IdentityServer4.EntityFramework.Entities.ApiScope>()
                .FirstOrDefaultAsync(o => o.Id == input.Id);
            await _identityServerRepository.RemoveApiScopeAsync(apiScope);
        }
        public async Task<IdentityServer4.EntityFramework.Entities.ApiScope> GetApiScopeAsync(int id)
        {
            var data = await QueryService.GetQueryable<IdentityServer4.EntityFramework.Entities.ApiScope>()
                .Include(x => x.UserClaims)
                .Include(x => x.Properties)
                .FirstOrDefaultAsync(o => o.Id == id);
            return data;
        }
        public async Task<IPagedList<IdentityServer4.EntityFramework.Entities.ApiScope>> GetApiScopePagedListAsync(GetApiScopeListPagingAndSortingQueryDto input)
        {
            var data = await QueryService.GetQueryable<IdentityServer4.EntityFramework.Entities.ApiScope>()
                .Include(x => x.UserClaims)
                .Include(x => x.Properties)
                .Sorting(input.Sorting)
                .ToPagedListAsync(input.PageIndex, input.PageSize);
            return data;
        }
        #endregion
        #region IdentityResource
        [UnitOfWork]
        public async Task CreateIdentityResourceAsync(CreateIdentityResourceDto input)
        {
            var identityResource = new IdentityServer4.Models.IdentityResource(input.Name,input.Claims.SplitToList()); 
            await _identityServerRepository.AddIdentityResourceAsync(identityResource.ToEntity());
        }
        [UnitOfWork]
        public async Task UpdateIdentityResourceAsync(UpdateIdentityResourceDto input)
        {
            var identityResource = await QueryService.GetQueryable<IdentityServer4.EntityFramework.Entities.IdentityResource>()
                .Include(x => x.UserClaims)
                .Include(x => x.Properties)
                .FirstOrDefaultAsync(o => o.Id == input.Id);
            var updateModel = new IdentityServer4.Models.IdentityResource(input.Name, input.Claims.SplitToList()).ToEntity();
            identityResource.UserClaims = updateModel.UserClaims;
        }
        [UnitOfWork]
        public async Task DeleteIdentityResourceAsync(DeleteIdentityResourceDto input)
        {
            var identityResource = await QueryService.GetQueryable<IdentityServer4.EntityFramework.Entities.IdentityResource>()
                .FirstOrDefaultAsync(o => o.Id == input.Id);
            await _identityServerRepository.RemoveIdentityResourceAsync(identityResource);
        }
        public async Task<IdentityServer4.EntityFramework.Entities.IdentityResource> GetIdentityResourceAsync(int id)
        {
            var data = await QueryService.GetQueryable<IdentityServer4.EntityFramework.Entities.IdentityResource>()
                .Include(x => x.UserClaims)
                .Include(x => x.Properties)
                .FirstOrDefaultAsync(o => o.Id == id);
            return data;
        }
        public async Task<IPagedList<IdentityServer4.EntityFramework.Entities.IdentityResource>> GetIdentityResourcePagedListAsync(GetIdentityResourceListPagingAndSortingQueryDto input)
        {
            var data = await QueryService.GetQueryable<IdentityServer4.EntityFramework.Entities.IdentityResource>()
                .Include(x => x.UserClaims)
                .Include(x => x.Properties)
                .Sorting(input.Sorting)
                .ToPagedListAsync(input.PageIndex, input.PageSize);
            return data;
        }
        #endregion
        public async Task<Resources> GetAllResourcesAsync()
        {
            var identity = QueryService.GetQueryable<IdentityServer4.EntityFramework.Entities.IdentityResource>()
               .Include(x => x.UserClaims)
               .Include(x => x.Properties);
            var apis = QueryService.GetQueryable<IdentityServer4.EntityFramework.Entities.ApiResource>()
                .Include(x => x.Secrets)
                .Include(x => x.Scopes)
                .Include(x => x.UserClaims)
                .Include(x => x.Properties)
                .AsNoTracking();
            var scopes = QueryService.GetQueryable<IdentityServer4.EntityFramework.Entities.ApiScope>()
                .Include(x => x.UserClaims)
                .Include(x => x.Properties)
                .AsNoTracking();
            return new Resources(
                (await identity.ToArrayAsync()).Select(o => o.ToModel()),
                (await apis.ToArrayAsync()).Select(o => o.ToModel()),
                (await scopes.ToArrayAsync()).Select(o => o.ToModel())
            );
        }
    }
}